package api

import (
	"bytes"
	"context"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/leo-xin/novel_distribution_sdk_go/common"
	"gitee.com/leo-xin/novel_distribution_sdk_go/wending/h5/config"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
)

type APIClient struct {
	Cfg    *config.Configuration
	common service // 复用单个结构体，而不是为每个服务在堆上分配一个
	mws    common.Middleware

	// API Services
	SiteListApi     *SiteListApiService
	LinkQueryApi    *LinkQueryApiService
	GetUserDataApi  *GetUserDataApiService
	RechargeListApi *RechargeListApiService
}

type service struct {
	client *APIClient
}

func NewAPIClient(cfg *config.Configuration) *APIClient {
	if cfg.HTTPClient == nil {
		cfg.HTTPClient = http.DefaultClient
	}

	c := &APIClient{}
	c.Cfg = cfg
	c.common.client = c

	// API Services
	c.SiteListApi = (*SiteListApiService)(&c.common)
	c.LinkQueryApi = (*LinkQueryApiService)(&c.common)
	c.GetUserDataApi = (*GetUserDataApiService)(&c.common)
	c.RechargeListApi = (*RechargeListApiService)(&c.common)

	return c
}

func (c *APIClient) Use(mws ...common.Middleware) {
	// Put the original middlewares to the first
	middlewares := make([]common.Middleware, 0, 1+len(mws))
	if c.mws != nil {
		middlewares = append(middlewares, c.mws)
	}
	middlewares = append(middlewares, mws...)
	c.mws = common.Chain(middlewares...)
}

// getSing 获取签名
func (c *APIClient) getSing(ctx context.Context, params map[string]string) string {

	params["secretkey"] = ctx.Value(config.SecretKey).(string)

	var keys = make([]string, 0, len(params))
	for k := range params {
		if k == "sign" {
			continue
		}
		keys = append(keys, k)
	}
	sort.Strings(keys)

	str := ""
	for _, k := range keys {
		v := params[k]
		str += k + "=" + v
	}
	md5Ctx := md5.New()
	md5Ctx.Write([]byte(str))
	signString := md5Ctx.Sum(nil)
	return strings.ToLower(hex.EncodeToString(signString))
}

// callAPI do the request.
func (c *APIClient) callAPI(ctx context.Context, request *http.Request) (*http.Response, error) {
	if c.mws != nil {
		return c.mws(c.do)(ctx, request)
	}
	return c.do(ctx, request)
}

func (c *APIClient) call(ctx context.Context, request *http.Request, response interface{}) (*http.Response, error) {
	httpResp, err := c.callAPI(ctx, request)
	if err != nil || httpResp == nil {
		return httpResp, err
	}
	body, err := io.ReadAll(httpResp.Body)
	if err != nil {
		return nil, err
	}
	if httpResp.Body.Close() != nil {
		return nil, err
	}
	httpResp.Body = io.NopCloser(bytes.NewBuffer(body))
	if err != nil {
		return httpResp, err
	}
	if httpResp.StatusCode >= 300 {
		newErr := &GenericWenDingH5Error{
			body:  body,
			error: httpResp.Status,
		}
		return httpResp, newErr
	}
	err = c.decode(&response, body)
	if err != nil {
		newErr := &GenericWenDingH5Error{
			body:  body,
			error: err.Error(),
		}
		return httpResp, newErr
	}
	return httpResp, nil
}

func (c *APIClient) do(ctx context.Context, req *http.Request) (*http.Response, error) {
	// add context to the request
	req = req.WithContext(ctx)
	return c.Cfg.HTTPClient.Do(req)
}

func (c *APIClient) prepareCtx(ctx context.Context) context.Context {
	if c.Cfg.LogEnable {
		ctx = context.WithValue(ctx, config.ContextEnableLog, true)
	}
	return ctx
}

// GetConfig 允许修改底层配置以供替代实现和测试
// 注意:在运行时修改配置可能会导致数据竞争和潜在的不必要的行为
func (c *APIClient) GetConfig() *config.Configuration {
	return c.Cfg
}

func (c *APIClient) buildRequest(
	ctx context.Context,
	path string,
	method string,
	postBody map[string]string,
	headerParams map[string]string,
	queryParams url.Values,
	formParams url.Values) (localVarRequest *http.Request, err error) {

	var body *bytes.Buffer

	ts := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
	consumerKey := ctx.Value(config.ConsumerKey).(string)

	// Detect postBody type and post.
	if postBody != nil {
		contentType := headerParams["Content-Type"]
		if contentType == "" {
			headerParams["Content-Type"] = "application/json; charset=utf-8"
		}
		body, err = c.setBody(postBody, contentType)
		if err != nil {
			return nil, err
		}
	}

	if len(formParams) > 0 {
		// 添加 form表单 请求参数
		if strings.HasPrefix(headerParams["Content-Type"], "multipart/form-data") {
			if body != nil {
				return nil, errors.New("cannot specify postBody and multipart form at the same time")
			}
			body = &bytes.Buffer{}
			w := multipart.NewWriter(body)

			for k, v := range formParams {
				for _, iv := range v {
					err := w.WriteField(k, iv)
					if err != nil {
						return nil, err
					}
				}
			}

			headerParams["Content-Type"] = w.FormDataContentType()
			headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len())
			err := w.Close()
			if err != nil {
				return nil, err
			}
		}

		if strings.HasPrefix(headerParams["Content-Type"], "application/x-www-form-urlencoded") {
			if body != nil {
				return nil, errors.New("cannot specify postBody and x-www-form-urlencoded form at the same time")
			}
			body = &bytes.Buffer{}
			body.WriteString(formParams.Encode())
			// Set Content-Length
			headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len())
		}
	}

	// 设置路径和查询参数
	urlObj, err := url.Parse(path)
	if err != nil {
		return nil, err
	}

	if c.Cfg.Host != "" {
		urlObj.Host = c.Cfg.Host
	}

	if c.Cfg.Scheme != "" {
		urlObj.Scheme = c.Cfg.Scheme
	}

	queryParams.Add("timestamp", ts)
	queryParams.Add("consumerkey", consumerKey)
	SingParams := make(map[string]string)
	for key, values := range queryParams {
		SingParams[key] = strings.Join(values, ",")
	}
	sign := c.getSing(ctx, SingParams)
	queryParams.Add("sign", sign)

	// 对查询参数进行编码并设置到 RawQuery
	urlObj.RawQuery = queryParams.Encode()

	// 生成请求
	if body != nil {
		localVarRequest, err = http.NewRequest(method, urlObj.String(), body)
	} else {
		localVarRequest, err = http.NewRequest(method, urlObj.String(), nil)
	}
	if err != nil {
		return nil, err
	}

	// 添加默认请求头
	for header, value := range c.Cfg.DefaultHeader {
		localVarRequest.Header.Add(header, value)
	}

	// 添加请求头
	if len(headerParams) > 0 {
		headers := http.Header{}
		for h, v := range headerParams {
			headers[h] = []string{v}
		}
		localVarRequest.Header = headers
	}

	if ctx != nil {
		// 添加 context
		localVarRequest = localVarRequest.WithContext(ctx)

		// 身份验证
	}

	return localVarRequest, nil
}

func (c *APIClient) decode(v interface{}, b []byte) (err error) {
	if len(b) == 0 {
		return nil
	}
	if s, ok := v.(*string); ok {
		*s = string(b)
		return nil
	}

	if err = json.Unmarshal(b, v); err != nil {
		return err
	}
	return nil
}

// 设置 request body
func (c *APIClient) setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) {
	if bodyBuf == nil {
		bodyBuf = &bytes.Buffer{}
	}

	err = json.NewEncoder(bodyBuf).Encode(body)

	if err != nil {
		return nil, err
	}

	if bodyBuf.Len() == 0 {
		err = fmt.Errorf("invalid body type %s\n", contentType)
		return nil, err
	}
	return bodyBuf, nil
}

func toString(val interface{}) string {
	b, _ := json.Marshal(val)
	value := string(b)
	return value
}

func hasReqParam(param interface{}, describe string) (bool, *GenericWenDingH5Error) {
	paramStr := toString(param)
	if paramStr == "null" {
		return false, &GenericWenDingH5Error{error: "1003", msg: describe + "参数必传"}
	}
	return true, nil
}

func hasStartTimeAndEndTime(startTime *uint64, endTime *uint64) (bool, *GenericWenDingH5Error) {
	if startTime == nil {
		return false, &GenericWenDingH5Error{error: "1003", msg: "StartTime参数必传"}
	}
	if endTime == nil {
		return false, &GenericWenDingH5Error{error: "1003", msg: "EndTime参数必传"}
	}
	return true, nil
}
